package com.stone.starter.core.helper.copy;

import com.stone.starter.core.constant.StoneCoreConstants;
import net.sf.cglib.beans.BeanCopier;
import net.sf.cglib.core.Converter;
import org.springframework.beans.BeanUtils;
import org.springframework.objenesis.Objenesis;
import org.springframework.objenesis.ObjenesisStd;
import org.springframework.objenesis.instantiator.ObjectInstantiator;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 实体类属性拷贝工具
 *
 * @author Mr_wenpan@163.com 2021/9/7 2:08 下午
 */
public class PropertiesCopier {
    private static final ConcurrentHashMap<String, BeanCopier> COPIER_CACHE = new ConcurrentHashMap<>();

    public static <O, T> T mapper(O source, Class<T> targetClass) {
        return copyProperties(source, targetClass, null);
    }

    public static <O, T> T mapper(O source, Class<T> targetClass,
                                  CopierAction<T> copierAction) {
        T target = copyProperties(source, targetClass, null);
        copierAction.run(target);
        return target;
    }

    public static <O, T> T mapper(O source, Class<T> targetClass,
                                  Converter converter) {
        return copyProperties(source, targetClass, converter);
    }

    public static <O, T> T mapper(O source, Class<T> targetClass,
                                  Converter converter,
                                  CopierAction<T> copierAction) {
        T target = copyProperties(source, targetClass, converter);
        copierAction.run(target);
        return target;
    }

    public static <O, T> void mapperObject(O source, T target) {
        copyObject(source, target, null);
    }

    public static <O, T> void mapperObject(O source, T target,
                                           CopierAction<T> copierAction) {
        copyObject(source, target, null);
        copierAction.run(target);
    }

    public static <O, T> void mapperObject(O source, T target,
                                           Converter converter) {
        copyObject(source, target, converter);
    }

    public static <O, T> void mapperObject(O source, T target, Converter converter,
                                           CopierAction<T> copierAction) {
        copyObject(source, target, converter);
        copierAction.run(target);
    }

    public static <O, T> List<T> listMapper(List<O> sourceList, Class<T> targetClass) {
        List<T> targetList = new ArrayList<>();
        sourceList.forEach(source -> {
            targetList.add(mapper(source, targetClass));
        });
        return targetList;
    }

    public static <O, T> List<T> listMapper(List<O> sourceList, Class<T> targetClass, CopierAction<T> copierAction) {
        List<T> targetList = new ArrayList<T>();
        sourceList.forEach(source -> {
            targetList.add(mapper(source, targetClass, copierAction));
        });
        return targetList;
    }

    public static <O, T> List<T> listMapper(List<O> sourceList, Class<T> targetClass,
                                            Converter converter) {
        List<T> targetList = new ArrayList<>();
        sourceList.forEach(source -> {
            targetList.add(mapper(source, targetClass, converter));
        });
        return targetList;
    }

    public static <O, T> List<T> listMapper(List<O> sourceList, Class<T> targetClass,
                                            Converter converter, CopierAction<T> copierAction) {
        List<T> targetList = new ArrayList<>();
        sourceList.forEach(source -> {
            targetList.add(mapper(source, targetClass, converter, copierAction));
        });
        return targetList;
    }

    /**
     * DB Entity Copy Properties 专用
     *
     * @param source 源数据
     * @param target 目标数据
     * @param <O>    源数据类型
     * @param <T>    目标数据类型
     */
    public static <O, T> void copyEntities(O source, T target) {
        BeanUtils.copyProperties(source, target,
                StoneCoreConstants.EntityDomain.FIELD_CREATED_BY,
                StoneCoreConstants.EntityDomain.FIELD_CREATION_DATE,
                StoneCoreConstants.EntityDomain.FIELD_LAST_UPDATED_BY,
                StoneCoreConstants.EntityDomain.FIELD_LAST_UPDATE_DATE,
                StoneCoreConstants.EntityDomain.FIELD_OBJECT_VERSION_NUMBER);
    }

    /**
     * DB Entity Copy Properties 专用
     *
     * @param source       源数据
     * @param target       目标数据
     * @param copierAction 拷贝结束执行
     * @param <O>          源数据类型
     * @param <T>          目标数据类型
     */
    public static <O, T> void copyEntities(O source, T target,
                                           CopierAction<T> copierAction) {
        BeanUtils.copyProperties(source, target,
                StoneCoreConstants.EntityDomain.FIELD_CREATED_BY,
                StoneCoreConstants.EntityDomain.FIELD_CREATION_DATE,
                StoneCoreConstants.EntityDomain.FIELD_LAST_UPDATED_BY,
                StoneCoreConstants.EntityDomain.FIELD_LAST_UPDATE_DATE,
                StoneCoreConstants.EntityDomain.FIELD_OBJECT_VERSION_NUMBER);
        copierAction.run(target);
    }

    private static <O, T> T copyProperties(O source, Class<T> targetClass,
                                           Converter converter) {
        String copierKey = generateKey(source.getClass(), targetClass, converter);
        BeanCopier copier;
        if (!COPIER_CACHE.containsKey(copierKey)) {
            if (converter == null) {
                copier = BeanCopier.create(source.getClass(), targetClass, false);
            } else {
                copier = BeanCopier.create(source.getClass(), targetClass, true);
            }
            COPIER_CACHE.put(copierKey, copier);
        } else {
            copier = COPIER_CACHE.get(copierKey);
        }

        Objenesis objenesis = new ObjenesisStd();
        ObjectInstantiator<T> objectInstant = objenesis.getInstantiatorOf(targetClass);
        T targetInstance = objectInstant.newInstance();
        copier.copy(source, targetInstance, converter);
        return targetInstance;
    }

    private static <O, T> void copyObject(O source, T target, Converter converter) {
        String copierKey = generateKey(source.getClass(), target.getClass(), converter);
        BeanCopier copier;
        if (!COPIER_CACHE.containsKey(copierKey)) {
            if (converter == null) {
                copier = BeanCopier.create(source.getClass(), target.getClass(), false);
            } else {
                copier = BeanCopier.create(source.getClass(), target.getClass(), true);
            }
            COPIER_CACHE.put(copierKey, copier);
        } else {
            copier = COPIER_CACHE.get(copierKey);
        }
        copier.copy(source, target, converter);
    }

    private static String generateKey(Class<?> sourceClass, Class<?> targetClass,
                                      Converter converter) {
        if (converter != null) {
            return sourceClass.toString() + "," + targetClass.toString() + "," + converter.toString();
        } else {
            return sourceClass.toString() + "," + targetClass.toString();
        }
    }

}